#include <linux/module.h>
#include <linux/kernel.h> /* for using printk */
#include <linux/fs.h> /* for register_chrdev */
#include <linux/slab.h>
#include <linux/mm.h> /* for mallocs also */
#include <linux/errno.h>
#include <stdio.h>
#include <asm/uaccess.h> /* for copy_to/from_user */
#include <assert.h>

#include "seq.h"

MODULE_LICENSE("GPL");

/**************** Defines ********************/
#define CUR_VAL_DEFAULT_VALUE (0)
#define D_DEFAULT_VALUE (1)
#define Q_DEFAULT_VALUE (2)
#define MODE_DEFAULT_VALUE ARITH_MODE
/*********************************************/

/***************** Types *********************/
typedef struct {
	unsigned int cur_val;
	unsigned int prev_val;
	unsigned int d;
	unsigned int q;
	unsigned int mode;
} seq_device;
/*********************************************/

/*************** Globals *********************/
int seq_devices;
unsigned int  major;
const char* module_name = "seq";



MODULE_PARM(seq_devices,"i");
MODULE_PARM_DESC(seq_devices,"number of devices");



/********* Declerations of methods ***********/
int seq_open (struct inode *inode, struct file *f);
int seq_release (struct inode *, struct file *);
ssize_t seq_read (struct file *, char *, size_t, loff_t *);
ssize_t seq_write (struct file*, const char *, size_t, loff_t *);
int seq_ioctl (struct inode*, struct file*, unsigned int, unsigned long);
loff_t seq_llseek (struct file *, loff_t, int);
unsigned int calculate_next_seq(seq_device*);
void reset_device(seq_device *dev);

/* don't know if it's any good */
/* struct module *owner; */



struct file_operations my_fops = {
	.llseek = seq_llseek,
	.read = seq_read,
	.write = seq_write,
	.open = seq_open,
	.flush = NULL,
	.release = seq_release,
	.ioctl = seq_ioctl
};

/* initialize the module */
int init_module(void) {
	SET_MODULE_OWNER(&my_fops);
	major = register_chrdev(0,module_name,&my_fops);
	return 0;
}

/* unregister the drivers */
void cleanup_module(void) {
	unregister_chrdev(major,module_name);
	return;
}

int seq_open (struct inode *inode, struct file *filp) {
	int minor;
	if (!inode || ! filp) {
		return -ENODEV;
	}
	
	/* getting minor number */ 
	minor = MINOR(inode->i_rdev);
	
	if (minor < 0 || minor >= seq_devices) {
		/* minor number out of bounds */
		return -ENODEV;
	}
	
	filp->private_data = kmalloc(sizeof(seq_device),GFP_KERNEL);
	if (!filp->private_data) {
		/* could not allocate memory for private_data */
		return -ENOMEM;
	}
	
	seq_device *new_dev = (seq_device*) (filp->private_data);
	reset_device(new_dev);
	
	return 0;
}

int seq_release (struct inode *node, struct file *filp) {
	int minor;
	if (!node || !filp) {
		return -ENODEV;
	}
	minor = MINOR(node->i_rdev);
	if (minor < 0 || minor >= seq_devices) {
		/* minor out of bounds */
		return -ENODEV;
	}
	
	kfree(filp->private_data);
	
	return 0;
}

ssize_t seq_read(struct file * filp, char *buff, size_t count, loff_t
*offp) { 
	if (!filp || !buff) {
		/* some random errors */
		return -ENODEV;
	}
	
	if (count == 0) {
		/* read 0 numbers. Vacuous truth? Vacuous truth! */
		return 0;
	}
	unsigned long ret_from_copy;
	seq_device* dev = (seq_device*)(filp->private_data);
	int i;
	unsigned int *intBuff = (unsigned int*) buff;
	for (i = 0; i < count; i++) {
		ret_from_copy = (copy_to_user(intBuff++, &dev->cur_val, sizeof(unsigned
int)));
		if (ret_from_copy != 0) {
			/* failed copying to user. cur_val is undetermined */
			return -EFAULT;
		}
		calculate_next_seq(dev);
	}
	return count;
}



ssize_t seq_write (struct file *filp, const char *buff, size_t count, loff_t *offp) {
	if (!filp) {
		return -ENODEV;
	}
	if (count != sizeof(unsigned int)) {
		/* size of number is not size of integer */
		return -EINVAL;
	}
	unsigned int new_cur_val;
	if (0 != copy_from_user(&new_cur_val, buff, sizeof(unsigned int))) {
		/* failed copying from user */
		return -EFAULT;
	}
	((seq_device*)(filp->private_data))->cur_val = new_cur_val;
	((seq_device*)(filp->private_data))->prev_val = new_cur_val;
	return sizeof(unsigned int);
}

unsigned int calculate_next_seq(seq_device* dev) {
	if (dev->mode ==ARITH_MODE) {
		dev->cur_val = dev->cur_val + dev->d;
		return dev->cur_val;
	}
	dev->cur_val = dev->cur_val * dev->q;
	return dev->cur_val;
}

void reset_device(seq_device *dev){
	dev->cur_val = CUR_VAL_DEFAULT_VALUE;
	dev->prev_val = CUR_VAL_DEFAULT_VALUE;
	dev->d = D_DEFAULT_VALUE;
	dev->q = Q_DEFAULT_VALUE;
	dev->mode = MODE_DEFAULT_VALUE;
}


int seq_ioctl (struct inode *inode, struct file *filp, unsigned int opcode, unsigned long param) {
	if (!inode || !filp) {
		return -ENODEV;
	}
	seq_device* dev = (seq_device*)(filp->private_data);
	switch (opcode) {
	case SEQ_RESET:
		dev->cur_val = dev->prev_val;
		break;
	case SEQ_SWCH_MOD:
		return seq_ioctl(inode, filp, SEQ_CHG_MOD, ARITH_MODE + GEOM_MODE - dev->mode);
	case SEQ_CHG_MOD:
		if (param!=ARITH_MODE && param!=GEOM_MODE) {
			return -EINVAL; //TODO: BORIS: what's the error value for these situations?
		}
		dev->mode = param;
		break;
	case SEQ_CHG_D:
		if (param<0 || param>127) {
			return  -EINVAL;
		}
		dev->d = param;
		break;
	case SEQ_GET_D:
		return dev->d;
	case SEQ_CHG_Q:
		if (param<0 || param>127) {
			return  -EINVAL;
		}
		dev->q = param;
		break;
	case SEQ_GET_Q:
		return dev->q;
	default:
		return -EINVAL; /* unknown command given */
	}
	return 0; /* success! */
}

loff_t seq_llseek (struct file *file, loff_t temp1, int temp2) {
	/* cannot call llseek */
	return -ENOSYS;
}
